// Copyright (c) 2021 Presto Labs Pte. Ltd.
// Author: hyuan

#include "coin2/exchange/phemex/order_v1/parser.h"

#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>

#include "coin2/base/conversion.h"
#include "coin2/base/log.h"
#include "coin2/exchange/phemex/api_order/native_private_client.h"
#include "coin2/exchange/phemex/order_v1/error_code.h"
#include "coin2/exchange/util/json_util.h"

namespace coin2::exchange::phemex::order_v1 {

namespace {

using namespace coin2::exchange::phemex::order_v1::error_code;

using coin::proto::AccountBalance;
using coin::proto::AccountPosition;
using coin::proto::CancelOrderResponse;
using coin::proto::CurrencyTransferElement;
using coin::proto::FillType;
using coin::proto::ProductFillBundle;
using coin::proto::ProductOrderBundle;
using coin::proto::ProductOrderElement;
using coin::proto::SubmitOrderResponse;
using coin::proto::TransferRequestProto;
using coin2::exchange::base::order::IOrderContext;
using coin2::exchange::phemex::api_order::PhemexOrderSubTopicId;
using coin2::exchange::phemex::symbology::PhemexCurrency;
using coin2::exchange::phemex::symbology::PhemexProduct;

using JsonValue = rapidjson::GenericValue<rapidjson::UTF8<>>;

inline int64_t GetProcOrderId(const JsonValue& data) {
  if (data.HasMember("clOrdID") && data["clOrdID"].IsString()) {
    if (!data["clOrdID"].IsNull()) {
      return stoll(data["clOrdID"].GetString());
    }
  }
  return 0;
}

inline coin::proto::OrderSide GetOrderSide(const std::string& side) {
  if (side == "Buy") {
    return coin::proto::OrderSide::BUY_ORDER;
  } else if (side == "Sell") {
    return coin::proto::OrderSide::SELL_ORDER;
  } else {
    return coin::proto::OrderSide::UNKNOWN_ORDER_SIDE;
  }
}

inline coin::proto::OrderType GetOrderType(const std::string& type) {
  if (type == "Limit") {
    return coin::proto::OrderType::LIMIT_ORDER;
  } else if (type == "Market") {
    return coin::proto::OrderType::MARKET_ORDER;
  } else {
    return coin::proto::OrderType::UNKNOWN_ORDER_TYPE;
  }
}

void SetOrderStatus(ProductOrderElement* order, const std::string& status) {
  if (status == "Rejected") {
    order->set_is_live(false);
  } else if (
      (status == "New") || (status == "Created") || (status == "PartiallyFilled") ||
      (status == "Untriggered") || (status == "Triggered")) {
    order->set_is_live(true);
  } else if (status == "Canceled") {
    order->set_cancel_confirmed(true);
  } else if (status == "Filled") {
    order->set_fully_filled(true);
  } else {
    LOG(ERROR) << "Unknown order status!" << status;
  }
}

bool IsPhemexErrMessage(const JsonValue& doc) {
  if (doc.HasMember("code")) {
    return (std::string(doc["code"].GetString()) != "0");
  }

  return false;
}

bool IsOrderUpdate(const rapidjson::GenericValue<rapidjson::UTF8<>>& json) {
  if (json.HasMember("orders")) {
    return json["orders"].HasMember("closed") || json["orders"].HasMember("fills") ||
           json["orders"].HasMember("open");
  }
  return false;
}

bool IsWalletUpdate(const rapidjson::GenericValue<rapidjson::UTF8<>>& json) {
  return json.HasMember("wallets");
}

}  // namespace

void PhemexParser::ParseRestAccountBalance() {
  /*
  {
      "code": 0,
      "msg": "",
      "data": [{
          "currency": "BTC",
          "balanceEv": 0,
          "lockedTradingBalanceEv": 0,
          "lockedWithdrawEv": 0,
          "lastUpdateTimeNs": 0,
      }]
  }
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestAccountBalance] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  auto account_balance = ParseBalance(document);
  if (!account_balance.has_exchange() || !account_balance.has_market_type()) {
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }
  result_.set_type(MsgType::ACCOUNT_BALANCE);
  *result_.mutable_account_balance() = std::move(account_balance);
}

// NOTE: when field exchange and market_type are set, which means parsed successfully
AccountBalance PhemexParser::ParseBalance(const JsonValue& doc) {
  AccountBalance acc_balance;

  if (doc.IsObject() && IsPhemexErrMessage(doc)) {
    LOG(ERROR) << "[ParseBalance] Error message\n" << JsonToString(doc);
    return acc_balance;
  }

  if (!doc.HasMember("data") || !doc["data"].IsArray()) {
    LOG(ERROR) << "[ParseBalance] Invalid data format\n" << JsonToString(doc);
    return acc_balance;
  }

  acc_balance.set_exchange("Phemex");
  acc_balance.set_market_type("Spot");
  for (const auto& data : doc["data"].GetArray()) {
    if (!data.HasMember("currency") || !data.HasMember("balanceEv") ||
        !data.HasMember("lockedTradingBalanceEv") || !data.HasMember("lockedWithdrawEv")) {
      continue;
    }
    if (!symcache_->has_currency_native(data["currency"].GetString())) {
      continue;
    }
    auto* currency = symcache_->GetCurrencyByNative(data["currency"].GetString());
    auto* currency_balance = acc_balance.add_each_balance();
    currency_balance->set_currency(currency->currency());
    currency_balance->set_currency_native(currency->native_currency());
    auto total = stod(data["balanceEv"].GetString()) / 1e8;
    auto hold = (stod(data["lockedTradingBalanceEv"].GetString()) +
                 stod(data["lockedWithdrawEv"].GetString())) /
                1e8;
    currency_balance->set_total(total);
    currency_balance->set_hold(hold);
    currency_balance->set_available(total - hold);
  }

  return acc_balance;
}

void PhemexParser::ParseRestOpenOrders() {
  /*
  {
    "code": "0",
    "msg": "",
    "data": [{
      "orderID": "69c23db5-93f2-4256-88b4-653da1e6575d",
      "clOrdID": "1634096080734103296",
      "priceEp": "5000000000000",
      "action": "New",
      "execInst": "None",
      "trigger": "UNSPECIFIED",
      "pegPriceType": "UNSPECIFIED",
      "stopDirection": "UNSPECIFIED",
      "bizError": "0",
      "symbol": "sBTCUSDT",
      "side": "Buy",
      "baseQtyEv": "20000",
      "ordType": "Limit",
      "timeInForce": "GoodTillCancel",
      "ordStatus": "New",
      "cumFeeEv": "0",
      "cumBaseQtyEv": "0",
      "cumQuoteQtyEv": "0",
      "leavesBaseQtyEv": "20000",
      "leavesQuoteQtyEv": "1000000000",
      "avgPriceEp": "0",
      "cumBaseAmountEv": "0",
      "cumQuoteAmountEv": "0",
      "quoteQtyEv": "1000000000",
      "qtyType": "ByBase",
      "stopPxEp": "0",
      "pegOffsetValueEp": "0"
    }]
  }
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestOpenOrders] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (document.IsObject() && IsPhemexErrMessage(document)) {
    LOG(ERROR) << "[ParseRestOpenOrders] Error message\n" << JsonToString(document);
    return;
  }

  if (!document.HasMember("data") || !document["data"].IsArray()) {
    LOG(ERROR) << "[ParseRestOpenOrders] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : document["data"].GetArray()) {
    if (!data.HasMember("qtyType")) {
      LOG(ERROR) << "[ParseRestOpenOrders] qtyType not found";
      continue;
    }
    if (std::string(data["qtyType"].GetString()) != "ByBase") {
      LOG(WARNING) << "[ParseRestOpenOrders] Non-ByBase qtyType found";
      continue;
    }
    auto order = product_order_bundle->add_each_order();
    order->set_exchange_type(ExchangeType::Phemex);
    order->set_market_type(MarketType::Spot);
    order->set_api_version("v1");
    order->set_exchange_order_id(data["orderID"].GetString());
    order->set_symbol(data["symbol"].GetString());
    order->set_price(stod(data["priceEp"].GetString()) / 1e8);
    order->set_qty(stod(data["baseQtyEv"].GetString()) / 1e8);
    order->set_proc_order_id(GetProcOrderId(data));
    order->set_side(GetOrderSide(data["side"].GetString()));
    order->set_order_type(GetOrderType(data["ordType"].GetString()));
    order->set_timestamp(GetCurrentTimestamp());
    SetOrderStatus(order, data["ordStatus"].GetString());
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void PhemexParser::ParseRestHistoryOrders() {
  /*
  {
      "code": 0,
      "msg": "OK",
      "data": {
          "total": 2,
          "rows": [
          {
              "orderID": "b2b7018d-f02f-4c59-b4cf-051b9c2d2e83",
              "stopPxEp": 0,
              "avgPriceEp": 970056000000,
              "qtyType": "ByQuote",
              "leavesBaseQtyEv": 0,
              "leavesQuoteQtyEv": 0,
              "baseQtyEv": "0",
              "feeCurrency": "1",
              "stopDirection": "UNSPECIFIED",
              "symbol": "sBTCUSDT",
              "side": "Buy",
              "quoteQtyEv": 1000000000,
              "priceEp": 970056000000,
              "ordType": "Limit",
              "timeInForce": "GoodTillCancel",
              "ordStatus": "Filled",
              "execStatus": "MakerFill",
              "createTimeNs": 1589449348601287000,
              "cumFeeEv": 0,
              "cumBaseValueEv": 103000,
              "cumQuoteValueEv": 999157680
          }]
      }
  }
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestHistoryOrders] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (document.IsObject() && IsPhemexErrMessage(document)) {
    LOG(ERROR) << "[ParseRestHistoryOrders] Error message\n" << JsonToString(document);
    return;
  }

  if (!document.HasMember("data") || !document["data"].HasMember("rows") ||
      !document["data"]["rows"].IsArray()) {
    LOG(ERROR) << "[ParseRestHistoryOrders] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto product_order_bundle = result_.mutable_order_list();
  product_order_bundle->Clear();
  bool product_order_bundle_found = false;
  for (const auto& data : document["data"]["rows"].GetArray()) {
    if (!data.HasMember("qtyType")) {
      LOG(ERROR) << "[ParseRestHistoryOrders] qtyType not found";
      continue;
    }
    if (std::string(data["qtyType"].GetString()) != "ByBase") {
      LOG(WARNING) << "[ParseRestHistoryOrders] Non-ByBase qtyType found";
      continue;
    }
    auto order = product_order_bundle->add_each_order();
    order->set_exchange_type(ExchangeType::Phemex);
    order->set_market_type(MarketType::Spot);
    order->set_api_version("v1");
    order->set_exchange_order_id(data["orderID"].GetString());
    order->set_symbol(data["symbol"].GetString());
    order->set_price(stod(data["priceEp"].GetString()) / 1e8);
    order->set_qty(stod(data["baseQtyEv"].GetString()) / 1e8);
    order->set_proc_order_id(GetProcOrderId(data));
    order->set_side(GetOrderSide(data["side"].GetString()));
    order->set_order_type(GetOrderType(data["ordType"].GetString()));
    order->set_timestamp(stoll(data["createTimeNs"].GetString()));
    SetOrderStatus(order, data["ordStatus"].GetString());
    product_order_bundle_found = true;
  }

  if (product_order_bundle_found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }
}

void PhemexParser::ParseRestFillOrders() {
  /*
  {
      "code": 0,
      "msg": "OK",
      "data": {
          "total": 1,
          "rows": [
          {
              "qtyType": "ByQuote",
              "transactTimeNs": 1589450974800550100,
              "clOrdID": "8ba59d40-df25-d4b0-14cf-0703f44e9690",
              "orderID": "b2b7018d-f02f-4c59-b4cf-051b9c2d2e83",
              "symbol": "sBTCUSDT",
              "side": "Buy",
              "priceEP": 970056000000,
              "baseQtyEv": 0,
              "quoteQtyEv": 1000000000,
              "action": "New",
              "execStatus": "MakerFill",
              "ordStatus": "Filled",
              "ordType": "Limit",
              "execInst": "None",
              "timeInForce": "GoodTillCancel",
              "stopDirection": "UNSPECIFIED",
              "tradeType": "Trade",
              "stopPxEp": 0,
              "execId": "c6bd8979-07ba-5946-b07e-f8b65135dbb1",
              "execPriceEp": 970056000000,
              "execBaseQtyEv": 103000,
              "execQuoteQtyEv": 999157680,
              "leavesBaseQtyEv": 0,
              "leavesQuoteQtyEv": 0,
              "execFeeEv": 0,
              "feeRateEr": 0
          }]
      }
  }
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestFillOrders] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (document.IsObject() && IsPhemexErrMessage(document)) {
    LOG(ERROR) << "[ParseRestFillOrders] Error message\n" << JsonToString(document);
    return;
  }

  if (!document.HasMember("data") || !document["data"].HasMember("rows") ||
      !document["data"]["rows"].IsArray()) {
    LOG(ERROR) << "[ParseRestFillOrders] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : document["data"]["rows"].GetArray()) {
    if (!data.HasMember("qtyType")) {
      LOG(ERROR) << "[ParseRestFillOrders] qtyType not found";
      continue;
    }
    if (std::string(data["qtyType"].GetString()) != "ByBase") {
      LOG(WARNING) << "[ParseRestFillOrders] Non-ByBase qtyType found";
      continue;
    }
    auto fill = product_fill_bundle->add_each_fill();
    fill->set_symbol(data["symbol"].GetString());
    fill->set_exchange_order_id(data["orderID"].GetString());
    fill->set_price(stod(data["execPriceEp"].GetString()) / 1e8);
    fill->set_qty(stod(data["execBaseQtyEv"].GetString()) / 1e8);
    fill->set_fill_id(data["execId"].GetString());
    if (data.HasMember("execFeeEv") && !data["execFeeEv"].IsNull() &&
        data.HasMember("feeCurrency") && !data["feeCurrency"].IsNull()) {
      fill->set_fee(stod(data["execFeeEv"].GetString()) / 1e8);
      fill->set_fee_currency(data["feeCurrency"].GetString());
    }
    std::string exec_status = data["execStatus"].GetString();
    if (exec_status == "MakerFill") {
      fill->set_fill_type(FillType::MAKER_FILL_TYPE);
    } else if (exec_status == "TakerFill") {
      fill->set_fill_type(FillType::TAKER_FILL_TYPE);
    } else {
      fill->set_fill_type(FillType::UNKNOWN_FILL_TYPE);
    }
    product_fill_bundle_found = true;
  }

  if (product_fill_bundle_found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void PhemexParser::ParseRestTransferSpot(const std::string& extra_info) {
  /*
  {
      "code":0,
      "msg":"OK",
      "data":{
          "rows":[
              {
                  "id":323724,
                  "fromUserId":2060211,
                  "toUserId":2329372,
                  "currency":"USDT",
                  "amountEv":10000000000,
                  "status":10,
                  "requestKey":"a599e9e9-9587-4222-b7f7-85157176a2a0",
                  "createAt":1681455876000
              }
          ]
      }
  }
  */
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestTransferSpot] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (document.IsObject() && IsPhemexErrMessage(document)) {
    LOG(ERROR) << "[ParseRestTransferSpot] Error message\n" << JsonToString(document);
    return;
  }

  if (!document.HasMember("data") || !document["data"].HasMember("rows") ||
      !document["data"]["rows"].IsArray()) {
    LOG(ERROR) << "[ParseRestTransferSpot] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto currency_transfer_bundle = result_.mutable_currency_transfer_bundle();
  currency_transfer_bundle->Clear();
  bool currency_transfer_bundle_found = false;
  for (const auto& data : document["data"]["rows"].GetArray()) {
    static std::vector<std::string> fields =
        {"id", "fromUserId", "toUserId", "currency", "amountEv", "status", "createAt"};
    if (!JsonFieldCheck(data, fields)) {
      LOG(ERROR) << "[ParseRestTransferSpot] Invalid transfer format" << JsonToString(data);
      continue;
    }

    auto create_timestamp = stoll(data["createAt"].GetString()) * 1e6;
    auto currency =
        PhemexCurrency::FromNativeStrImpl(data["currency"].GetString(), create_timestamp);
    std::string status = data["status"].GetString();
    std::string id = data["id"].GetString();
    std::string record_key =
        std::string("ParseRestTransferSpot") + std::to_string(create_timestamp) + id;

    // Skip it if duplicate
    if (!transfer_first_record_) {
      if (status == transfer_record_map_[record_key]) {
        continue;
      }
    }

    transfer_record_map_[record_key] = status;
    auto proto_status = TransferRequestProto::TRANSFER_SUBMITTED;
    // 0 pending  10 successful  11 failed
    if (status == "0") {
      proto_status = TransferRequestProto::TRANSFER_ACCEPTED;
    } else if (status == "10") {
      proto_status = TransferRequestProto::TRANSFER_CONFIRMED;
    } else if (status == "11") {
      proto_status = TransferRequestProto::TRANSFER_REJECTED;
    } else {
      LOG(ERROR) << "[ParseRestTransferSpot] Invalid transfer status" << JsonToString(data);
      continue;
    }

    auto* currency_transfer = currency_transfer_bundle->add_each_transfer();
    currency_transfer->set_transfer_id(id);
    currency_transfer->set_from_address(data["fromUserId"].GetString());
    currency_transfer->set_to_address(data["toUserId"].GetString());
    currency_transfer->set_currency(currency->currency());
    currency_transfer->set_qty(stod(data["amountEv"].GetString()) / 1e8);
    currency_transfer->set_status(proto_status);
    currency_transfer->set_create_timestamp(create_timestamp);

    if (master_account_id_ == std::string(data["fromUserId"].GetString())) {
      currency_transfer->set_type(TransferRequestProto::WITHDRAW_SUBACCOUNT);
    } else if (master_account_id_ == std::string(data["toUserId"].GetString())) {
      currency_transfer->set_type(TransferRequestProto::DEPOSIT_SUBACCOUNT);
    } else {
      LOG(ERROR) << "[ParseRestTransferSpot] unkown transfer type " << JsonToString(data);
      continue;
    }

    currency_transfer_bundle_found = true;
  }

  if (!transfer_first_record_) {
    if (transfer_init_set_.count("ParseRestTransferSpot") == 0) {
      transfer_init_set_.insert("ParseRestTransferSpot");
      return;
    }
  }

  if (currency_transfer_bundle_found) {
    result_.set_type(MsgType::TRANSFER);
  }
}

void PhemexParser::ParseRestTransferFutures(const std::string& extra_info) {
  /*
  {
    "code": 0,
    "msg": "OK",
    "data": {
      "rows": [
        {
          "fromUserId": 2060211,
          "toUserId": 4705420,
          "amountEv": 50000000000,
          "currency": "USDT",
          "bizCode": 3,
          "requestKey": "A2AT813215bb1548419c9c89b930cf36c85f",
          "createAt": 1681093672000
        }
      ]
    }
  }
*/
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestTransferFutures] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (document.IsObject() && IsPhemexErrMessage(document)) {
    LOG(ERROR) << "[ParseRestTransferFutures] Error message\n" << JsonToString(document);
    return;
  }

  if (!document.HasMember("data") || !document["data"].HasMember("rows") ||
      !document["data"]["rows"].IsArray()) {
    LOG(ERROR) << "[ParseRestTransferFutures] Invalid data format\n" << JsonToString(document);
    return;
  }

  auto currency_transfer_bundle = result_.mutable_currency_transfer_bundle();
  currency_transfer_bundle->Clear();
  bool currency_transfer_bundle_found = false;
  for (const auto& data : document["data"]["rows"].GetArray()) {
    static std::vector<std::string> fields =
        {"fromUserId", "toUserId", "currency", "amountEv", "bizCode", "createAt"};
    if (!JsonFieldCheck(data, fields)) {
      LOG(ERROR) << "[ParseRestTransferFutures] Invalid transfer format" << JsonToString(data);
      continue;
    }

    auto create_timestamp = stoll(data["createAt"].GetString()) * 1e6;
    auto currency =
        PhemexCurrency::FromNativeStrImpl(data["currency"].GetString(), create_timestamp);
    std::string biz_code = data["bizCode"].GetString();
    std::string record_key = extra_info + std::to_string(create_timestamp);

    std::string record_val = "ParseRestTransferFutures";
    // Skip it if duplicate
    if (!transfer_first_record_) {
      if (record_val == transfer_record_map_[record_key]) {
        continue;
      }
    }

    transfer_record_map_[record_key] = record_val;
    auto proto_status = TransferRequestProto::TRANSFER_CONFIRMED;
    auto proto_type = TransferRequestProto::WITHDRAW_SUBACCOUNT;
    if (biz_code == "3") {
      proto_type = TransferRequestProto::WITHDRAW_SUBACCOUNT;
    } else if (biz_code == "4") {
      proto_type = TransferRequestProto::DEPOSIT_SUBACCOUNT;
    } else {
      LOG(ERROR) << "[ParseRestTransferFutures] Invalid transfer type" << JsonToString(data);
    }

    auto* currency_transfer = currency_transfer_bundle->add_each_transfer();
    currency_transfer->set_from_address(data["fromUserId"].GetString());
    currency_transfer->set_to_address(data["toUserId"].GetString());
    currency_transfer->set_currency(currency->currency());
    currency_transfer->set_qty(stod(data["amountEv"].GetString()) / 1e8);
    currency_transfer->set_status(proto_status);
    currency_transfer->set_create_timestamp(create_timestamp);
    currency_transfer->set_type(proto_type);
    currency_transfer_bundle_found = true;
  }

  if (!transfer_first_record_) {
    if (transfer_init_set_.count("ParseRestTransferFutures") == 0) {
      transfer_init_set_.insert("ParseRestTransferFutures");
      return;
    }
  }

  if (currency_transfer_bundle_found) {
    result_.set_type(MsgType::TRANSFER);
  }
}

/*
    {
      "id": 884116,
      "address": "0xc71F2a65827f65E96763C0FF82f976073365De92",
      "amountRv": "100000",
      "chainCode": 4,
      "chainName": "ETH",
      "currency": "CCX",
      "currencyCode": 357,
      "email": "prestoinvt@gmail.com",
      "expiredAt": 1674093158000,
      "feeRv": "60",
      "nickName": null,
      "phone": null,
      "rejectReason": "",
      "submitedAt": 1674091358000,
      "txHash": "0x6a0487b7188bc03cac7c7bb1d65e3ece3a8cf094e0c62ccbfcfa1556139351cd",
      "userId": 2060211,
      "status": "Succeed"
    }
*/

void PhemexParser::ParseRestWithdraw() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestWithdraw] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (document.IsObject() && IsPhemexErrMessage(document)) {
    LOG(ERROR) << "[ParseRestWithdraw] Error message\n" << JsonToString(document);
    return;
  }

  if (!document.HasMember("data") || !document["data"].IsArray()) {
    LOG(ERROR) << "[ParseRestWithdraw] Invalid data format\n" << JsonToString(document);
    return;
  }

  // TODO(hyuan): Response is not as described; further test is needed.
  auto currency_transfer_bundle = result_.mutable_currency_transfer_bundle();
  currency_transfer_bundle->Clear();
  bool currency_transfer_bundle_found = false;
  for (const auto& data : document["data"].GetArray()) {
    static std::vector<std::string> fields =
        {"currency", "status", "amountRv", "submitedAt", "id", "address"};
    if (!JsonFieldCheck(data, fields)) {
      LOG(ERROR) << "[ParseRestWithdraw] Invalid transfer format " << JsonToString(data)
                 << std::endl;
      continue;
    }

    auto create_timestamp = stoll(data["submitedAt"].GetString()) * 1e6;
    auto currency =
        PhemexCurrency::FromNativeStrImpl(data["currency"].GetString(), create_timestamp);

    std::string status = data["status"].GetString();
    std::string id = data["id"].GetString();
    std::string record_key =
        std::string("ParseRestWithdraw") + std::to_string(create_timestamp) + id;
    // Skip it if duplicate
    if (!transfer_first_record_) {
      if (status == transfer_record_map_[record_key]) {
        continue;
      }
    }

    transfer_record_map_[record_key] = status;
    auto* currency_transfer = currency_transfer_bundle->add_each_transfer();
    currency_transfer->set_currency(currency->currency());
    auto status_type = TransferRequestProto::TRANSFER_SUBMITTED;
    currency_transfer->set_type(TransferRequestProto::WITHDRAW);
    if (status == "Succeed") {
      currency_transfer->set_status(TransferRequestProto::TRANSFER_CONFIRMED);
    } else if (
        status == "Rejected" || status == "Security check failed" || status == "Address Risk" ||
        status == "Expired") {
      currency_transfer->set_status(TransferRequestProto::TRANSFER_REJECTED);
    } else if (
        status == "Security Checking" || status == "Pending Review" ||
        status == "Pending Transfer") {
      currency_transfer->set_status(TransferRequestProto::TRANSFER_ACCEPTED);
    } else if (status == "Cancelled") {
      currency_transfer->set_status(TransferRequestProto::TRANSFER_CONFIRMED);
    }

    currency_transfer->set_status(status_type);
    if (data.HasMember("txHash") && !data["txHash"].IsNull()) {
      currency_transfer->set_external_transfer_id(data["txHash"].GetString());
    }
    currency_transfer->set_transfer_id(id);
    currency_transfer->set_qty(stod(data["amountRv"].GetString()));
    currency_transfer->set_create_timestamp(create_timestamp);
    currency_transfer->set_to_address(data["address"].GetString());
    if (data.HasMember("feeRv") && !data["feeRv"].IsNull()) {
      double fee = stod(data["feeRv"].GetString());
      currency_transfer->set_fee(fee);
      currency_transfer->mutable_detail_fee()->set_fee(fee);
      currency_transfer->mutable_detail_fee()->set_fee_currency(data["currency"].GetString());
    }

    currency_transfer_bundle_found = true;
  }

  if (!transfer_first_record_) {
    if (transfer_init_set_.count("ParseRestWithdraw") == 0) {
      transfer_init_set_.insert("ParseRestWithdraw");
      return;
    }
  }

  if (currency_transfer_bundle_found) {
    result_.set_type(MsgType::TRANSFER);
  }
}  // namespace coin2::exchange::phemex::order_v1

void PhemexParser::ParseRestDeposit() {
  /*
      {
        "depositTo": "SPOT",
        "address": "TD6DYG9jZ3gq2HKkjEDqGP8e24fy3pe2TT",
        "amountRv": "1000",
        "chainCode": 11,
        "chainName": "TRX",
        "confirmations": 21,
        "confirmedHeight": null,
        "createdAt": 1633941564000,
        "currency": "USDT",
        "currencyCode": 3,
        "email": "prestoinvt@gmail.com",
        "errorCode": null,
        "errMsg": null,
        "id": 626044,
        "needConfirmHeight": null,
        "status": "Success",
        "txHash": "485e9443ed08a5e5a6f1ad62998506786f7000ab08476fac3e554ed55e4e7043",
        "type": "Deposit",
        "userId": 2060211
      }
  */

  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseRestDeposit] Parsing error\n" << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  if (document.IsObject() && IsPhemexErrMessage(document)) {
    LOG(ERROR) << "[ParseRestDeposit] Error message\n" << JsonToString(document);
    return;
  }

  if (!document.HasMember("data") || !document["data"].IsArray()) {
    LOG(ERROR) << "[ParseRestDeposit] Invalid data format\n" << JsonToString(document);
    return;
  }

  // TODO(hyuan): Response is not as described; further test is needed.
  auto currency_transfer_bundle = result_.mutable_currency_transfer_bundle();
  currency_transfer_bundle->Clear();
  bool currency_transfer_bundle_found = false;
  for (const auto& data : document["data"].GetArray()) {
    static std::vector<std::string> fields =
        {"currency", "status", "amountRv", "type", "createdAt", "address", "id"};
    if (!JsonFieldCheck(data, fields)) {
      LOG(ERROR) << "[ParseRestDeposit] Invalid transfer format " << JsonToString(data)
                 << std::endl;
      continue;
    }

    auto create_timestamp = stoll(data["createdAt"].GetString()) * 1e6;
    std::string id = data["id"].GetString();
    auto currency =
        PhemexCurrency::FromNativeStrImpl(data["currency"].GetString(), create_timestamp);
    std::string status = data["status"].GetString();
    std::string record_key =
        std::string("ParseRestDeposit") + std::to_string(create_timestamp) + id;

    // Skip it if duplicate
    if (!transfer_first_record_) {
      if (status == transfer_record_map_[record_key]) {
        continue;
      }
    }

    transfer_record_map_[record_key] = status;
    auto* currency_transfer = currency_transfer_bundle->add_each_transfer();
    currency_transfer->set_currency(currency->currency());
    if (std::string(data["type"].GetString()) == "Deposit") {
      currency_transfer->set_type(TransferRequestProto::DEPOSIT);
    } else {
      LOG(ERROR) << "[ParseRestDeposit] Unknown transfer type";
    }

    auto type = TransferRequestProto::TRANSFER_CONFIRMED;
    if (status == "Success") {
      type = TransferRequestProto::TRANSFER_CONFIRMED;
    } else if (status == "Rejected" || status == "SecurityCheckFailed") {
      type = TransferRequestProto::TRANSFER_REJECTED;
    } else if (
        status == "SecurityChecking" || status == "AmlCsApporve" || status == "Confirmed" ||
        status == "New") {
      type = TransferRequestProto::TRANSFER_ACCEPTED;
    }

    currency_transfer->set_create_timestamp(create_timestamp);
    currency_transfer->set_status(type);
    if (data.HasMember("txHash") && !data["txHash"].IsNull()) {
      currency_transfer->set_external_transfer_id(data["txHash"].GetString());
    }

    currency_transfer->set_transfer_id(id);
    currency_transfer->set_qty(stod(data["amountRv"].GetString()));
    currency_transfer->set_from_address(data["address"].GetString());
    currency_transfer_bundle_found = true;
  }

  if (!transfer_first_record_) {
    if (transfer_init_set_.count("ParseRestDeposit") == 0) {
      transfer_init_set_.insert("ParseRestDeposit");
      return;
    }
  }

  if (currency_transfer_bundle_found) {
    result_.set_type(MsgType::TRANSFER);
  }

}  // namespace coin2::exchange::phemex::order_v1

void PhemexParser::ParseHeader(const std::shared_ptr<AsioHttpContext>& http_context) {
  const auto& header = http_context->res.base();
  rate_limit_spotorder_.success = false;
  enable_time_spotorder_.success = false;
  rate_limit_others_.success = false;
  enable_time_others_.success = false;

  if (header.count("x-ratelimit-remaining-spotOrder") > 0) {
    rate_limit_spotorder_.rate_limit_remain =
        std::stoi(header.at("x-ratelimit-remaining-spotOrder").data());
    rate_limit_spotorder_.success = true;
  }

  if (header.count("x-ratelimit-retry-after-spotOrder") > 0) {
    int32_t retry_after = std::stoi(header.at("x-ratelimit-retry-after-spotOrder").data());
    if (retry_after > 0) {
      enable_time_spotorder_.enable_time =
          (retry_after + 2) * 1'000'000'000LL + GetCurrentTimestamp();
      enable_time_spotorder_.success = true;
      LOG(INFO) << "phemex native client enable_time for spotOrder: "
                << enable_time_spotorder_.enable_time;
    }
  }

  if (header.count("x-ratelimit-remaining") > 0) {
    rate_limit_others_.rate_limit_remain = std::stoi(header.at("x-ratelimit-remaining").data());
    rate_limit_others_.success = true;
  }

  if (header.count("x-ratelimit-retry-after") > 0) {
    int32_t retry_after = std::stoi(header.at("x-ratelimit-retry-after").data());
    if (retry_after > 0) {
      enable_time_others_.enable_time = (retry_after + 2) * 1'000'000'000LL + GetCurrentTimestamp();
      enable_time_others_.success = true;
      LOG(INFO) << "phemex native client enable_time for others: "
                << enable_time_others_.enable_time;
    }
  }
}

void PhemexParser::ParseSubmitResponse() {
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::SUBMIT_RESPONSE);
  auto resp = result_.mutable_submit_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_timestamp(GetCurrentTimestamp());

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseSubmitResponse] AsioHttpContextStatus error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  const auto http_code = http_context->res.result_int();
  if (http_code == 429) {
    LOG(ERROR) << "[ParseSubmitResponse] API limit error";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_exchange_error_msg(http_context->res.body().data());
    resp->set_success(false);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseSubmitResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (json.HasMember("code") && json.HasMember("msg")) {
    auto error_code =
        GetOrderErrorCode(json["code"], json["msg"], order_context->has_exchange_order_id());
    resp->set_error_code(error_code);
    resp->set_exchange_error_code(GetExchangeOrderErrorCode(json));
    resp->set_exchange_error_msg(GetExchangeOrderErrorMsg(json));
    if (error_code != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
      LOG(ERROR) << "[ParseSubmitResponse] Error response\n" << http_context->res;
      resp->set_success(false);
      return;
    }
  }

  resp->set_success(true);
  if (json.HasMember("data") && json["data"].HasMember("symbol") &&
      json["data"].HasMember("orderID")) {
    resp->set_exchange_order_id(std::string(json["data"]["orderID"].GetString()));
  } else {
    LOG(ERROR) << "[ParseSubmitResponse] Invalid data format\n" << JsonToString(json);
  }
}

void PhemexParser::ParseCancelResponse() {
  const auto order_context = msg_->oc;
  const auto& http_context = msg_->http_context;
  if (!IsValidResponse(http_context)) {
    return;
  }

  result_.set_type(MsgType::CANCEL_RESPONSE);
  auto resp = result_.mutable_cancel_response();
  resp->Clear();
  resp->set_symbol(order_context->order_spec().product);
  resp->set_proc_order_id(order_context->proc_order_id());
  resp->set_http_status(http_context->res.result_int());
  resp->set_exchange_order_id(order_context->exchange_order_id());
  resp->set_timestamp(msg_->timestamp);

  if (http_context->status == AsioHttpContextStatus::kError) {
    LOG(ERROR) << "[ParseCancelResponse] AsioHttpContextStatus error";
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_NETWORK_ERROR);
    resp->set_success(false);
    return;
  }

  const auto http_code = http_context->res.result_int();
  if (http_code == 429 || http_code == 418) {
    LOG(ERROR) << "[ParseCancelResponse] API limit error";
    resp->set_error_code(coin::proto::OrderErrorCode::API_LIMIT_ERROR);
    resp->set_exchange_error_msg(http_context->res.body().data());
    resp->set_success(false);
    return;
  }

  JsonDocument json;
  if (!PrepareJsonDocument(&json)) {
    LOG(ERROR) << "[ParseCancelResponse] Parsing error\n" << http_context->res;
    resp->set_error_code(coin::proto::OrderErrorCode::ORDER_API_ERROR);
    resp->set_success(false);
    return;
  }

  if (json.HasMember("code") && json.HasMember("msg")) {
    auto error_code =
        GetOrderErrorCode(json["code"], json["msg"], order_context->has_exchange_order_id());
    resp->set_error_code(error_code);
    resp->set_exchange_error_code(GetExchangeOrderErrorCode(json));
    resp->set_exchange_error_msg(GetExchangeOrderErrorMsg(json));
    if (error_code != coin::proto::OrderErrorCode::ORDER_NO_ERROR) {
      LOG(ERROR) << "[ParseCancelResponse] Error response\n" << http_context->res;
      resp->set_success(false);
      return;
    }
  }

  resp->set_success(true);
  if (json.HasMember("data") && json["data"].HasMember("symbol") &&
      json["data"].HasMember("orderID")) {
    resp->set_exchange_order_id(std::string(json["data"]["orderID"].GetString()));
  } else {
    LOG(ERROR) << "[ParseCancelResponse] Invalid data format\n" << JsonToString(json);
  }
  resp->set_cancel_confirmed(true);
}

bool PhemexParser::ParseWsOpenOrders(const JsonValue& open_orders) {
  auto product_order_bundle = result_.mutable_order_list();
  bool product_order_bundle_found = false;
  for (const auto& data : open_orders.GetArray()) {
    const static std::vector<std::string> fields = {
        "qtyType",
        "action",
        "side",
        "orderID",
        "symbol",
        "priceEp",
        "baseQtyEv",
        "clOrdID",
        "ordType",
        "createTimeNs",
        "ordStatus"};
    if (!JsonFieldCheck(data, fields)) {
      LOG(ERROR) << "Invalid data format:" << JsonToString(data);
      continue;
    }
    if (std::string(data["qtyType"].GetString()) != "ByBase") {
      LOG(WARNING) << "[ParseWsOpenOrders] Non-ByBase qtyType found";
      continue;
    }
    if (std::string(data["action"].GetString()) != "New") {
      LOG(ERROR) << "Invalid data format:" << JsonToString(data);
      continue;
    }

    auto order = product_order_bundle->add_each_order();
    order->set_exchange_type(ExchangeType::Phemex);
    order->set_market_type(MarketType::Spot);
    order->set_api_version("v1");
    order->set_exchange_order_id(data["orderID"].GetString());
    order->set_symbol(data["symbol"].GetString());
    order->set_price(stod(data["priceEp"].GetString()) / 1e8);
    order->set_qty(stod(data["baseQtyEv"].GetString()) / 1e8);
    order->set_proc_order_id(GetProcOrderId(data));
    order->set_side(GetOrderSide(data["side"].GetString()));
    order->set_order_type(GetOrderType(data["ordType"].GetString()));
    order->set_timestamp(stoll(data["createTimeNs"].GetString()));
    SetOrderStatus(order, data["ordStatus"].GetString());
    product_order_bundle_found = true;
  }

  return product_order_bundle_found;
}

bool PhemexParser::ParseWsClosedOrders(const JsonValue& closed_orders) {
  auto product_order_bundle = result_.mutable_order_list();
  bool product_order_bundle_found = false;
  for (const auto& data : closed_orders.GetArray()) {
    const static std::vector<std::string> fields = {
        "qtyType",
        "action",
        "side",
        "orderID",
        "symbol",
        "priceEp",
        "baseQtyEv",
        "clOrdID",
        "ordType",
        "createTimeNs",
        "ordStatus"};
    if (!JsonFieldCheck(data, fields)) {
      LOG(ERROR) << "Invalid data format:" << JsonToString(data);
      continue;
    }
    if (std::string(data["qtyType"].GetString()) != "ByBase") {
      LOG(WARNING) << "[ParseWsClosedOrders] Non-ByBase qtyType found";
      continue;
    }
    std::string action = data["action"].GetString();
    std::string side = data["side"].GetString();
    if ((action != "Cancel" && action != "New") || (action == "Cancel" && side == "None")) {
      LOG(ERROR) << "Invalid data format:" << JsonToString(data);
      continue;
    }

    auto order = product_order_bundle->add_each_order();
    order->set_exchange_type(ExchangeType::Phemex);
    order->set_market_type(MarketType::Spot);
    order->set_api_version("v1");
    order->set_exchange_order_id(data["orderID"].GetString());
    order->set_symbol(data["symbol"].GetString());
    order->set_price(stod(data["priceEp"].GetString()) / 1e8);
    order->set_qty(stod(data["baseQtyEv"].GetString()) / 1e8);
    order->set_proc_order_id(GetProcOrderId(data));
    order->set_side(GetOrderSide(data["side"].GetString()));
    order->set_order_type(GetOrderType(data["ordType"].GetString()));
    order->set_timestamp(stoll(data["createTimeNs"].GetString()));
    SetOrderStatus(order, data["ordStatus"].GetString());
    product_order_bundle_found = true;
  }

  return product_order_bundle_found;
}

bool PhemexParser::ParseWsFillOrders(const JsonValue& fill_orders) {
  auto product_fill_bundle = result_.mutable_fill_list();
  product_fill_bundle->Clear();
  bool product_fill_bundle_found = false;
  for (const auto& data : fill_orders.GetArray()) {
    const static std::vector<std::string> fields = {
        "qtyType",
        "action",
        "orderID",
        "symbol",
        "execPriceEp",
        "execBaseQtyEv",
        "execID",
        "execFeeEv",
        "feeCurrency"};
    if (!JsonFieldCheck(data, fields)) {
      LOG(ERROR) << "Invalid data format:" << JsonToString(data);
      continue;
    }
    if (std::string(data["qtyType"].GetString()) != "ByBase") {
      LOG(WARNING) << "[ParseWsFillOrders] Non-ByBase qtyType found";
      continue;
    }
    if (std::string(data["action"].GetString()) != "New") {
      LOG(ERROR) << "Invalid data format:" << JsonToString(data);
      continue;
    }

    auto fill = product_fill_bundle->add_each_fill();
    fill->set_symbol(data["symbol"].GetString());
    fill->set_exchange_order_id(data["orderID"].GetString());
    fill->set_price(stod(data["execPriceEp"].GetString()) / 1e8);
    fill->set_qty(stod(data["execBaseQtyEv"].GetString()) / 1e8);
    fill->set_fill_id(data["execID"].GetString());
    fill->set_fee(stod(data["execFeeEv"].GetString()) / 1e8);
    fill->set_fee_currency(data["feeCurrency"].GetString());
    product_fill_bundle_found = true;
  }

  return product_fill_bundle_found;
}

void PhemexParser::ParseWsOrderInfoList(const JsonDocument& document) {
  result_.mutable_order_list()->Clear();
  bool found = false;
  if (document["orders"].HasMember("open") && document["orders"]["open"].IsArray()) {
    found = found || ParseWsOpenOrders(document["orders"]["open"]);
  }
  if (document["orders"].HasMember("closed") && document["orders"]["closed"].IsArray()) {
    found = found || ParseWsClosedOrders(document["orders"]["closed"]);
  }
  if (found) {
    result_.set_type(MsgType::ORDER_STATUS);
  }

  found = false;
  if (document["orders"].HasMember("fills") && document["orders"]["fills"].IsArray()) {
    found = ParseWsFillOrders(document["orders"]["fills"]);
  }
  if (found) {
    result_.set_type(MsgType::ORDER_FILL);
  }
}

void PhemexParser::ParseWsWalletInfoList(const JsonDocument& document) {
  auto acc_balance = result_.mutable_account_balance();
  acc_balance->Clear();
  acc_balance->set_exchange("Phemex");
  acc_balance->set_market_type("Spot");
  bool acc_balance_found = false;
  for (const auto& data : document["wallets"].GetArray()) {
    if (!data.HasMember("currency") || !data.HasMember("balanceEv") ||
        !data.HasMember("lockedTradingBalanceEv") || !data.HasMember("lockedWithdrawEv")) {
      continue;
    }
    if (!symcache_->has_currency_native(data["currency"].GetString())) {
      continue;
    }
    auto* currency = symcache_->GetCurrencyByNative(data["currency"].GetString());
    auto* currency_balance = acc_balance->add_each_balance();
    currency_balance->set_currency(currency->currency());
    currency_balance->set_currency_native(currency->native_currency());
    auto total = stod(data["balanceEv"].GetString()) / 1e8;
    auto hold = (stod(data["lockedTradingBalanceEv"].GetString()) +
                 stod(data["lockedWithdrawEv"].GetString())) /
                1e8;
    currency_balance->set_total(total);
    currency_balance->set_hold(hold);
    currency_balance->set_available(total - hold);

    if (data.HasMember("userID")) {
      user_id = data["userID"].GetString();
      mb_->PublishEvent("phemex/user_id", &user_id);
    }

    acc_balance_found = true;
  }

  if (acc_balance_found) {
    result_.set_type(MsgType::ACCOUNT_BALANCE);
  }
}

void PhemexParser::ParseWsMessage() {
  JsonDocument document;
  if (!PrepareJsonDocument(&document)) {
    LOG(ERROR) << "[ParseWsMessage] Parsing Error. " << msg_->DebugString();
    result_.set_error_code(OrderErrorCode::INVALID_JSON);
    return;
  }

  DLOG(INFO) << "OG ws msg: " << JsonToString(document);
  if (IsOrderUpdate(document)) {
    ParseWsOrderInfoList(document);
  }
  if (IsWalletUpdate(document)) {
    ParseWsWalletInfoList(document);
  }
}

auto PhemexParser::ParseRawMessage(const RawMessage& msg) -> const ParseResult& {
  try {
    result_.Clear();
    msg_ = &msg;
    sub_topic_id_ = static_cast<OrderSubTopicId>(msg_->type);

    if (msg.type == OrderSubTopicId::WS_MESSAGE) {
      ParseWsMessage();
      return result_;
    }

    CHECK(msg.type == OrderSubTopicId::REST_MESSAGE);

    if (msg.http_context->query_type != OrderSubTopicId::TRANSFER) {
      ParseHeader(msg.http_context);
      if (rate_limit_spotorder_.success) {
        mb_->PublishEvent(
            "phemex/rate_limit_spotorder",
            reinterpret_cast<void*>(rate_limit_spotorder_.rate_limit_remain));
      }
      if (enable_time_spotorder_.success) {
        mb_->PublishEvent(
            "phemex/enable_time_spotorder",
            reinterpret_cast<void*>(enable_time_spotorder_.enable_time));
      }
      if (rate_limit_others_.success) {
        mb_->PublishEvent(
            "phemex/rate_limit_others",
            reinterpret_cast<void*>(rate_limit_others_.rate_limit_remain));
      }
      if (enable_time_others_.success) {
        mb_->PublishEvent(
            "phemex/enable_time_others",
            reinterpret_cast<void*>(enable_time_others_.enable_time));
      }
    }

    switch (msg.http_context->query_type) {
      case OrderSubTopicId::SUBMIT_RESPONSE:
        ParseSubmitResponse();
        break;
      case OrderSubTopicId::CANCEL_RESPONSE:
        ParseCancelResponse();
        break;
      case OrderSubTopicId::REST_ACCOUNT_BALANCE:
        ParseRestAccountBalance();
        break;
      case OrderSubTopicId::REST_OPEN_ORDERS:
        ParseRestOpenOrders();
        break;
      case OrderSubTopicId::REST_DONE_ORDERS:
        ParseRestHistoryOrders();
        break;
      case OrderSubTopicId::REST_FILL_ORDERS:
        ParseRestFillOrders();
        break;
      case PhemexOrderSubTopicId::REST_DEPOSIT:
        ParseRestDeposit();
        break;
      case PhemexOrderSubTopicId::REST_WITHDRAWAL:
        ParseRestWithdraw();
        break;
      case PhemexOrderSubTopicId::TRANSFER_FUTURES:
        ParseRestTransferFutures(msg.http_context->extra_info);
        break;
      case PhemexOrderSubTopicId::TRANSFER_SPOT:
        ParseRestTransferSpot(msg.http_context->extra_info);
        break;
      default: {
        LOG(ERROR) << "Parsing Error. " << msg_->DebugString();
        result_.set_error_code(OrderErrorCode::ORDER_UNKNOWN_ERROR);
        break;
      }
    }
  } catch (const std::exception& e) {
    LOG(ERROR) << "exception:" << e.what() << " msg:" << msg.DebugString();
    throw;
  } catch (...) {
    LOG(ERROR) << "unknown exception, msg:" << msg.DebugString();
    throw;
  }

  return result_;
}

}  // namespace coin2::exchange::phemex::order_v1
